﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TrayMes
{
    public delegate void ConnectStatusChangedHandler(object sender, SocketMesEventArgs e);
    public class TrayToMes : System.ComponentModel.INotifyPropertyChanged
    {
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (null != PropertyChanged)
            {
                this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
        private bool firstQurety = true;
        private System.Net.WebSockets.ClientWebSocket socket;
        private int msgId = 1;
        private readonly System.Collections.Generic.List<string> msgRs = new System.Collections.Generic.List<string>();
        private Uri serverUri;
        public event ConnectStatusChangedHandler OnConnectStatusChanged;
        private readonly ManualResetEvent mEventCheckConnect = new ManualResetEvent(false);
        private bool isConnected = false;
        private bool autoConnect = false;
        public string[] Seperators { get; set; }
        public bool IsConnected
        {
            get => this.isConnected;
            set
            {
                this.isConnected = value;
                this.OnPropertyChanged("IsConnected");
            }
        }
        public System.Net.WebSockets.WebSocketState State
        {
            get
            {
                if (null == socket)
                {
                    return System.Net.WebSockets.WebSocketState.None;
                }
                return socket.State;
            }
        }
        public string[] MessageRS
        {
            get => msgRs.ToArray();
        }
        public bool AutoConnect
        {
            get => this.autoConnect;
            set
            {
                this.autoConnect = value;
                this.OnPropertyChanged("AutoConnect");
                if (this.autoConnect)
                {
                    this.mEventCheckConnect.Set();
                }
                else
                {
                    this.mEventCheckConnect.Reset();
                }
            }
        }
        public async Task<WebSocketState> Open(Uri uri)
        {
            Task taskOpen;
            if (null == socket
                || (System.Net.WebSockets.WebSocketState.Open != socket.State
                && System.Net.WebSockets.WebSocketState.Connecting != socket.State))
            {
                this.serverUri = uri;
                socket = new System.Net.WebSockets.ClientWebSocket();
                this.firstQurety = true;
                try
                {
                    serverUri = uri;
                    taskOpen = this.socket.ConnectAsync(uri, System.Threading.CancellationToken.None);
                    this.OnStatusChanged(
                    new SocketMesEventArgs()
                    {
                        Message = $"Try to connect to server.",
                        State = SocketState.Opening,
                        Uri = this.serverUri?.ToString()
                    });
                    await taskOpen;
                    if (taskOpen.IsCompleted)
                    {
                        this.OnStatusChanged(
                            new SocketMesEventArgs()
                            {
                                Message = $"Server Connected:{this.socket.State}",
                                State = WebSocketState.Open == this.socket.State ? SocketState.Connected : SocketState.Disconnected,
                                Uri = this.serverUri.ToString()
                            });
                    }
                    if (taskOpen.IsFaulted)
                    {
                        throw taskOpen.Exception;
                    }
                }
                catch (Exception ex)
                {
                    this.msgRs.Clear();
                    this.msgRs.Add($"Error on connect to server.\n{ex.Message}\n Server:{this.serverUri}");
                    this.OnStatusChanged(
                        new SocketMesEventArgs()
                        {
                            Message = $"Error on connect to server:{this.socket.State}\n{ex.Message}",
                            State = WebSocketState.Open == this.socket.State ? SocketState.Connected : SocketState.Disconnected,
                            Uri = this.serverUri.ToString(),
                        });
                }
            }
            else
            {
                this.OnStatusChanged(
                    new SocketMesEventArgs()
                    {
                        Message = $"Web Socket State:{this.socket.State}",
                        State = WebSocketState.Connecting == this.socket.State ? SocketState.Opening : WebSocketState.Open == this.socket.State ? SocketState.Connected : SocketState.Disconnected,
                        Uri = this.serverUri.ToString()
                    });
            }
            if (this.autoConnect)
            {
                this.mEventCheckConnect.Set();
            }
            else
            {
                this.mEventCheckConnect.Reset();
            }
            return this.socket.State;
        }
        private void OnStatusChanged(SocketMesEventArgs e)
        {
            if (null != this.OnConnectStatusChanged)
            {
                this.OnConnectStatusChanged(this, e);
            }
        }
        public void Close()
        {
            try
            {
                this.socket.CloseAsync(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure,
                    "CloseClientWebSocket",
                    System.Threading.CancellationToken.None);
            }
            catch
            {
                throw;
            }
        }
        public int GetCountLimit(string station, string item)
        {
            this.msgRs.Clear();
            //Task<int> task = QueryMes(Command.GetValidCount, palletId, palletType, Operation.Get);
            //await task;
            //return task.Result;
            return QueryCountLimitFromMes(Command.GetCountLimit, station, item);
        }
        public int GetPalletCountValue(string palletId, string palletType)
        {
            this.msgRs.Clear();
            //Task<int> task = QueryMes(Command.GetCurrentCount, palletId, palletType, Operation.Get);
            //await task;
            //return task.Result;
            return QueryCountValueFromMes(Command.GetCountValue, palletId, palletType, Operation.Get);
        }
        public string GetPalletPmPeriod(string station, string item)
        {
            this.msgRs.Clear();
            return QueryPmPeriodFromMes(Command.GetCountLimit, station, item);
        }
        public DateTime GetPalletPmValue(string palletId, string palletType)
        {
            this.msgRs.Clear();
            return QueryPmValueFromMes(Command.GetPmValue, palletId, palletType, Operation.Get);
        }
        public int GetPalletCountLimit(string station, string item)
        {
            this.msgRs.Clear();
            return QueryCountLimitFromMes(Command.GetCountLimit, station, item);
        }
        public int GetPalletValidTimes(string palletId, string palletType)
        {
            this.msgRs.Clear();
            int setTimes = GetPalletCountValue(palletId, palletType);
            int curTimes = GetPalletCountLimit(palletId, palletType);
            return setTimes - curTimes;
        }
        public int QueryCountValueFromMes(Command cmd, string palletId, string palletType, Operation op)
        {
            int val = -1;
            string request = $"{msgId++},{GetEnumDescription.GetDescriptionByEnum<DescriptionAttribute>(cmd).Description},{palletId},{palletType},{op.ToString()}";
            //Task<string> query = QueryFromMes(request);
            //await query;
            //string response = query.Result;
            string response = QueryFromMes(request);
            string[] tmp = response.Split(Seperators, StringSplitOptions.None);
            if (tmp.Length > 3 && tmp[2] == "True")
            {
                try
                {
                    if (string.IsNullOrEmpty(tmp[3]))
                    {
                        val = 0;
                    }
                    else
                    {
                        val = int.Parse(tmp[3]);
                    }
                }
                catch
                {
                    throw;
                }
            }
            return val;
        }
        private int QueryCountLimitFromMes(Command cmd, string station, string item)
        {
            int val = -1;
            string request = $"{msgId++},{GetEnumDescription.GetDescriptionByEnum<DescriptionAttribute>(cmd).Description},/GHPAdapter_Config/{station}/CARRIER_ASSIGN,{item}";
            //Task<string> query = QueryFromMes(request);
            //await query;
            //string response = query.Result;
            string response = QueryFromMes(request);
            string[] tmp = response.Split(Seperators, StringSplitOptions.None);
            if (tmp.Length > 3 && tmp[2] == "True")
            {
                try
                {
                    if (string.IsNullOrEmpty(tmp[3]))
                    {
                        val = -1;
                    }
                    else
                    {
                        val = int.Parse(tmp[3]);
                    }
                }
                catch
                {
                    throw;
                }
            }
            return val;
        }
        public DateTime QueryPmValueFromMes(Command cmd, string palletId, string palletType, Operation op)
        {
            DateTime val = new DateTime(1, 1, 1);
            string request = $"{msgId++},{GetEnumDescription.GetDescriptionByEnum<DescriptionAttribute>(cmd).Description},{palletId},{palletType},{op.ToString()}";
            //Task<string> query = QueryFromMes(request);
            //await query;
            //string response = query.Result;
            string response = QueryFromMes(request);
            string[] tmp = response.Split(Seperators, StringSplitOptions.None);
            if (tmp.Length > 3 && tmp[2] == "True")
            {
                try
                {
                    if (string.IsNullOrEmpty(tmp[3]))
                    {
                        val = DateTime.Now;
                    }
                    else
                    {
                        val = DateTime.Parse(tmp[3]);
                    }
                }
                catch
                {
                    throw;
                }
            }
            return val;
        }
        private string QueryPmPeriodFromMes(Command cmd, string station, string item)
        {
            string val = string.Empty;
            string request = $"{msgId++},{GetEnumDescription.GetDescriptionByEnum<DescriptionAttribute>(cmd).Description},/GHPAdapter_Config/{station}/CARRIER_ASSIGN,{item}";
            //Task<string> query = QueryFromMes(request);
            //await query;
            //string response = query.Result;
            string response = QueryFromMes(request);
            string[] tmp = response.Split(Seperators, StringSplitOptions.None);
            if (tmp.Length > 3 && tmp[2] == "True")
            {
                val = tmp[3] ?? string.Empty;
            }
            return val;
        }
        private string QueryFromMes(string request)
        {
            string response = string.Empty;
            ArraySegment<byte> sockData;
            //Task taskSend;
            System.Threading.CancellationTokenSource ctsSend = new System.Threading.CancellationTokenSource();
            System.Threading.CancellationTokenSource ctsRecv = new System.Threading.CancellationTokenSource();

            //Task<System.Net.WebSockets.WebSocketReceiveResult> taskRecv;
            sockData = new ArraySegment<byte>(System.Text.Encoding.ASCII.GetBytes(request));
            if (System.Net.WebSockets.WebSocketState.Open != socket.State)
            {
                if (System.Net.WebSockets.WebSocketState.Connecting == socket.State)
                {
                    this.OnStatusChanged(new SocketMesEventArgs
                    {
                        Message = "Connection to server is establishing",
                        State = SocketState.Opening,
                        Uri = this.serverUri.ToString()
                    });
                    throw new Exception("Connection to server is establishing");
                }
                else
                {
                    //try
                    //{
                    this.OnStatusChanged(new SocketMesEventArgs
                    {
                        Message = "Connection is not established.",
                        State = SocketState.Disconnected,
                        Uri = this.serverUri.ToString()
                    });
                    return string.Empty;
                    //await socket.ConnectAsync(serverUri, System.Threading.CancellationToken.None);
                    //}
                    //catch
                    //{
                    //    this.OnStatusChanged(new SocketMesEventArgs
                    //    {
                    //        Message = "Connection is not established.",
                    //        State = SocketState.Error,
                    //        Uri = this.serverUri.ToString()
                    //    });
                    //    throw;
                    //}
                }
            }

            //taskSend = this.socket.SendAsync(sockData,
            //    System.Net.WebSockets.WebSocketMessageType.Text,
            //    true,
            //    cts.Token);
            //await taskSend;
            ctsSend.CancelAfter(2000);
            //try
            //{
            //    Task t = this.socket.SendAsync(sockData,
            //    System.Net.WebSockets.WebSocketMessageType.Text,
            //    true,
            //    ctsSend.Token);
            //    t.RunSynchronously();
            //    if (t.IsFaulted)
            //    {
            //        msgRs.Add($"Error: Exception on sending request to Mes.{t.Exception.Message}");
            //        throw t.Exception;
            //        //return;
            //    }
            //    if (t.IsCanceled)
            //    {
            //        msgRs.Add("Error: Failed to send request to Mes over 2 seconds.");
            //        return string.Empty;
            //    }
            //    if (t.IsCompleted)
            //    {
            //        this.msgRs.Add(request);
            //    }
            //}
            //catch (TaskCanceledException)
            //{
            //    msgRs.Add("Error: Failed to send request to Mes over 2 seconds.");
            //}
            //catch (Exception ex)
            //{
            //    msgRs.Add($"Error: Exception on receving data from Mes.{ex.Message}");
            //    throw;
            //}
            this.OnStatusChanged(new SocketMesEventArgs
            {
                Message = "Sending request to server",
                State = SocketState.Sending,
                Uri = this.serverUri.ToString()
            });
            this.socket.SendAsync(sockData,
                System.Net.WebSockets.WebSocketMessageType.Text,
                true,
                ctsSend.Token).ContinueWith(new Action<Task>(t =>
                //System.Threading.CancellationToken.None).ContinueWith(new Action<Task>(t =>
                {
                    if (t.IsFaulted)
                    {
                        this.OnStatusChanged(new SocketMesEventArgs
                        {
                            Message = "Error on sending request to server",
                            State = SocketState.Error,
                            Uri = this.serverUri.ToString()
                        });
                        msgRs.Add($"Error: Exception on sending request to Mes.{t.Exception.Message}");
                        throw t.Exception;
                        //return;
                    }
                    if (t.IsCanceled)
                    {
                        this.OnStatusChanged(new SocketMesEventArgs
                        {
                            Message = "Canceled work, Wating for work",
                            State = SocketState.Idle,
                            Uri = this.serverUri.ToString()
                        });
                        msgRs.Add("Error: Failed to send request to Mes over 2 seconds.");
                        return;
                    }
                    if (t.IsCompleted)
                    {
                        this.OnStatusChanged(new SocketMesEventArgs
                        {
                            Message = "Wating for work",
                            State = SocketState.Idle,
                            Uri = this.serverUri.ToString()
                        });
                        this.msgRs.Add(request);
                    }
                })).ConfigureAwait(false);
            //System.Threading.Thread.Sleep(this.firstQurety ? 20 : 5);
            this.firstQurety = false;
            //if (taskSend.IsFaulted)
            //{
            //    throw taskSend.Exception;
            //}
            //if (taskSend.IsCompleted)
            //{
            //this.msgRs.Add(request);
            byte[] recv = new byte[1024];
            sockData = new ArraySegment<byte>(recv);
            try
            {
                //taskRecv = this.socket.ReceiveAsync(sockData, cts.Token)
                this.OnStatusChanged(new SocketMesEventArgs
                {
                    Message = "Receving request from server",
                    State = SocketState.Receving,
                    Uri = this.serverUri.ToString()
                });
                //System.Threading.Thread.Sleep(1000);
                ctsRecv.CancelAfter(3000);
                WebSocketReceiveResult wrr = this.socket.ReceiveAsync(sockData, ctsRecv.Token).Result;
                this.OnStatusChanged(new SocketMesEventArgs
                {
                    Message = "Wating for work",
                    State = SocketState.Idle,
                    Uri = this.serverUri.ToString()
                });
                //System.Threading.Thread.Sleep(30);
                //this.socket.ReceiveAsync(sockData, ctsRecv.Token)
                //.ContinueWith(new Action<Task<WebSocketReceiveResult>>(t =>
                //{
                //    if (t.IsFaulted)
                //    {
                //        msgRs.Add($"Error: Fault on receiving from Mes.\n {t.Exception}");
                //    }
                //    if (t.IsCanceled)
                //    {
                //        msgRs.Add("Error: Server do not response over 3 seconds.");
                //    }
                //    if (t.IsCompleted)
                //    {
                //        //data = new byte[t.Result.Count];
                //        //System.Buffer.BlockCopy(data, 0, data, 0, data.Length);
                //        response = System.Text.Encoding.ASCII.GetString(recv, 0, t.Result.Count);
                //        msgRs.Add($"{response}({System.BitConverter.ToString(recv, 0, t.Result.Count)})");
                //    }
                //}));
                //await taskRecv;
                //WebSocketReceiveResult wrr = taskRecv.Result;
                //wrr = this.socket.ReceiveAsync(sockData, cts.Token).Result;
                response = System.Text.Encoding.ASCII.GetString(recv, 0, wrr.Count);
                msgRs.Add($"{response}({System.BitConverter.ToString(recv, 0, wrr.Count)})");
                msgRs.Add($"{response.Split(Seperators, StringSplitOptions.None).Length}:{response.Split(Seperators, StringSplitOptions.None)[2]}");
            }
            catch (TaskCanceledException)
            {
                msgRs.Add("Error: Server do not response over 5 seconds.");
            }
            catch(Exception ex)
            {
                msgRs.Add($"Error: Exception on sending request to Mes.{ex.Message}");
                throw;
            }
            //}
            return response;
        }
    }
}
